// 
// Bachelor of Software Engineering
// Media Design School
// Auckland
// New Zealand
//
// (c) 2005 - 2014 Media Design School
//
// File Name	: AlienWave.cpp
// Desrciption	: CAlienWave implementation file.
// Author		: Hayden Asplet.
// Mail			: hayden.asplet@mediadesignschool.com
//

// PCH
#include "SpaceInvadersStd.h"

// Library Includes

// This Include
#include "AlienWave.h"

// Local Includes
#include "SpaceInvadersObjectComponents\AlienComponent.h"


CAlienWave::CAlienWave()
	:m_waveBox(CRect(0,0,0,0)),
	m_eDirection(keRight),
	m_fMoveInterval(0.0f),
	m_fRate(g_kfALIEN_WAVE_START_RATE),
	m_iIncrementX(g_kiALIEN_WAVE_INCREMENT_X),
	m_iIncrementY(g_kiALIEN_WAVE_INCREMENT_Y),
	m_fFireBulletTimeBuffer(0.0f),
	m_fFireBulletTime(0.0f),
	m_bIsStopped(false)
{

}

CAlienWave::~CAlienWave()
{

}

void CAlienWave::Update(float _fDeltaTime)
{
	if(m_bIsStopped) {
	// Is the wave stopped?
		// Don't update if we are stopped.
		return;
	}

	// Add to timers.
	m_fMoveInterval += _fDeltaTime;
	m_fFireBulletTimeBuffer += _fDeltaTime;

	if(m_fMoveInterval > m_fRate) {
	// Is it time to move?
		// Set the time interval back to 0.
		m_fMoveInterval = 0.0f;

		CPoint amountMoved(m_waveBox.m_iX, m_waveBox.m_iY);

		if(m_eDirection == keRight) {
			m_waveBox.m_iX += m_iIncrementX;

			if(m_waveBox.GetRight() > g_pApp->GetScreenSize().m_iX) {
			// Is m_waveBox outside of the screen on the right?
				m_waveBox.m_iX -= m_waveBox.GetRight() - g_pApp->GetScreenSize().m_iX;

				// Move the wave down.
				m_waveBox.m_iY += m_iIncrementY;

				// Change Directions.
				m_eDirection = keLeft;
			}
		}
		else if(m_eDirection == keLeft) {
			m_waveBox.m_iX -= m_iIncrementX;

			if(m_waveBox.GetLeft() < 0) {
			// Is m_waveBox outside of the screen on the right?
				m_waveBox.m_iX += 0 - m_waveBox.GetLeft();

				// Move the wave down.
				m_waveBox.m_iY += m_iIncrementY;

				// Change Directions.
				m_eDirection = keRight;
			}
		}

		amountMoved.m_iX -= m_waveBox.m_iX;
		amountMoved.m_iY -= m_waveBox.m_iY;

		std::list<TGameObjectId>::iterator  iter;

		// Move the aliens.
		for(iter = m_alienIds.begin(); iter != m_alienIds.end(); ++iter) {
			// Get the current alien.
			CGameObject* pAlien = g_pApp->m_pGame->GetGameObject(*iter);

			if(pAlien) {
				// Retrieve the position and animation components of the alien.
				shared_ptr<CPositionComponent> pPositionComponent = pAlien->GetComponent<CPositionComponent>();
				shared_ptr<CAnimationComponent> pAnimComponent = pAlien->GetComponent<CAnimationComponent>();

				if(pAnimComponent) {
					// Change the alien's visual state.
					if(pAnimComponent->GetCurrentAnimation() == "State1") {
						pAnimComponent->PlayAnimation("State2", 0, true);
					}
					else {
						pAnimComponent->PlayAnimation("State1", 0, true);
					}
				}
				else {
				// There is no animation component for this alien, and therefore we cannot switch it's animation.
				}

				if(pPositionComponent) {
					// Update the aliens position with the amount moved.
					pPositionComponent->SetPosition(pPositionComponent->GetPosition() - amountMoved);
				}
				else {
				// Theres no position component to update the alien's position with.
				}
			}
		}
	}

	if(m_fFireBulletTime == 0.0f) {
		// Get a random number between the minimum time between bullet fires and the maximum.
		// The numbers are multiplied by 100 and casted to integers so that they can be used in the 
		// random number generated as integers. 
		int iRandom = GetRandomNumber(static_cast<int>(g_kfALIEN_WAVE_FIRE_MIN_TIME * 100.0f), 
									  static_cast<int>(g_kfALIEN_WAVE_FIRE_MAX_TIME * 100.0f));

		// Scale the random number back down so that it can be used as a float again.
		m_fFireBulletTime = static_cast<float>(iRandom) / 100.0f;
	}

	if(m_fFireBulletTimeBuffer >= m_fFireBulletTime) {
	// Is it time to fire a bullet?
		// Set the timers back.
		m_fFireBulletTimeBuffer = 0.0f;
		m_fFireBulletTime = 0.0f;

		// Select a random alien.
		int iRandomAlien = GetRandomNumber(0, m_alienIds.size());

		int iCount = 0;
		std::list<TGameObjectId>::iterator iter;

		// Loop through aliens until we find the iRandomAlien-ith one. 
		for(iter = m_alienIds.begin(); iter != m_alienIds.end(); ++iter, ++iCount) {
			if(iCount == iRandomAlien) {
			// Found the randomly selected alien.
				// Get the current alien.
				CGameObject* pAlien = g_pApp->m_pGame->GetGameObject(*iter);

				if(pAlien) {
					shared_ptr<CAlienComponent> pAlienComponent = pAlien->GetComponent<CAlienComponent>();

					if(pAlienComponent) {
						// Fire the bullet.
						pAlienComponent->FireBullet();
					}
					else {
					// No alien component, therefore cannot fire a bullet.
					}
				}

				// We found the random alien, break out of the loop.
				break;
			}
			else {
				// Continue.
			}
		}
	}
}

bool CAlienWave::IsWaveCompleted() const
{
	return m_alienIds.empty();
}

void CAlienWave::CreateWave(int _iX, int _iY, int _iNumOfAliensX, int _iNumOfAliensY, int _iOffset, int _iIncrementY)
{
	// Clear the aliens for a new wave.
	ClearWave();

	// Reconstruct the wave.
	*this = CAlienWave();

	m_waveBox.m_iX = _iX;
	m_waveBox.m_iY = _iY;

	m_iIncrementY = _iIncrementY;

	for(int i = 0; i < _iNumOfAliensX; ++i) {
		for(int j = 0; j < _iNumOfAliensY; ++j) {
			CGameObject* pAlien = 0;

			int iStartTime = timeGetTime();

			if(j == 0) {
				pAlien = g_pApp->m_pGame->CreateGameObject("GameObjects\\Alien3.xml");
			}
			else if (j < 3) {
				pAlien = g_pApp->m_pGame->CreateGameObject("GameObjects\\Alien2.xml");
			}
			else {
				pAlien = g_pApp->m_pGame->CreateGameObject("GameObjects\\Alien1.xml");
			}

			int iEndTime = timeGetTime() - iStartTime;

			if(pAlien) {
				shared_ptr<CPositionComponent> pPositionComponent = pAlien->GetComponent<CPositionComponent>();

				// Set the position of the alien.
				pPositionComponent->SetPosition(CPoint((i * _iOffset) + _iX, (j * _iOffset) + _iY));

				// Add the alien to the wave.
				m_alienIds.push_back(pAlien->GetId());
				
				// TO DO: Put re-align aliens so that they're centred.
				shared_ptr<CPhysicsComponent> pPhysicsComponent = pAlien->GetComponent<CPhysicsComponent>();

				if(pPhysicsComponent) {
					CPoint halfWidthHeight(pPhysicsComponent->GetBoundingBox().m_iWidth / 2, pPhysicsComponent->GetBoundingBox().m_iHeight / 2);

					pPositionComponent->SetPosition(pPositionComponent->GetPosition() - halfWidthHeight);

					if(i == _iNumOfAliensX - 1 && j == _iNumOfAliensY - 1) {
					// Is this the last alien?
						shared_ptr<CPhysicsComponent> pPhysicsComponent = pAlien->GetComponent<CPhysicsComponent>();

						m_waveBox.m_iWidth = pPhysicsComponent->GetWorldBoundingBox().GetRight() - m_waveBox.m_iX;
						m_waveBox.m_iHeight = pPhysicsComponent->GetWorldBoundingBox().GetBottom()- m_waveBox.m_iY;
					}
				}

				if(pPositionComponent->GetPosition().m_iX < m_waveBox.m_iX) {
					m_waveBox.m_iX = pPositionComponent->GetPosition().m_iX;
				}

				if(pPositionComponent->GetPosition().m_iY < m_waveBox.m_iY) {
					m_waveBox.m_iY = pPositionComponent->GetPosition().m_iY ;
				}
			}
		}
	}
}

void CAlienWave::DestroyAlien(TGameObjectId _objectId)
{
	// Remove the alien from the list of ids.
	m_alienIds.remove(_objectId);

	//==============================================================================
	// Now that an alien has been destroyed we may need to adjust the bounding box
	// of the wave appropriately so that it only fits all alive aliens.
	//==============================================================================

	CPoint bottomRight(0, 0);				 // The bottom right most position of the alien map.
	CPoint topLeft(g_pApp->GetScreenSize()); // The top left most position of the alien map.

	std::list<TGameObjectId>::iterator  iter;

	for(iter = m_alienIds.begin(); iter != m_alienIds.end(); ++iter) {
		// Get the current alien.
		CGameObject* pAlien = g_pApp->m_pGame->GetGameObject(*iter);

		if(pAlien) {
		// Alien object retrieved successfully.
			shared_ptr<CPhysicsComponent> pPhysicsComponent = pAlien->GetComponent<CPhysicsComponent>();

			if(pPhysicsComponent) {
				// Check to see if this alien is one of the very edge ones.
				if(pPhysicsComponent->GetWorldBoundingBox().GetRight() > bottomRight.m_iX) {
					bottomRight.m_iX = pPhysicsComponent->GetWorldBoundingBox().GetRight();
				}

				if(pPhysicsComponent->GetWorldBoundingBox().GetBottom() > bottomRight.m_iY) {
					bottomRight.m_iY = pPhysicsComponent->GetWorldBoundingBox().GetBottom();
				}

				if(pPhysicsComponent->GetWorldBoundingBox().m_iX < topLeft.m_iX) {
					topLeft.m_iX = pPhysicsComponent->GetWorldBoundingBox().m_iX;
				}

				if(pPhysicsComponent->GetWorldBoundingBox().m_iY < topLeft.m_iY) {
					topLeft.m_iY = pPhysicsComponent->GetWorldBoundingBox().m_iY;
				}
			}
			else {
				// Can't work out the bounding box if the alien doesn't have a physics component.
			}
		}
	}

	// We must first adjust the bottomRight coords so that they represent 
	// only the width and height of the waveBox.
	bottomRight -= topLeft;

	// Update the wave bounding box.
 	m_waveBox = CRect(topLeft.m_iX, topLeft.m_iY, bottomRight.m_iX, bottomRight.m_iY); 
}

void CAlienWave::Stop()
{
	m_bIsStopped = true;
}

void CAlienWave::Continue()
{
	m_bIsStopped = false;
}

void CAlienWave::ClearWave()
{
	while(!m_alienIds.empty()) {
		g_pApp->m_pGame->DestroyGameObject(m_alienIds.front());
		m_alienIds.pop_front();
	}
}

void CAlienWave::SetRate(float _fRate)
{
	m_fRate = _fRate;
}

float CAlienWave::GetRate() const
{
	return m_fRate;
}

bool CAlienWave::IsStopped() const
{
	return m_bIsStopped;
}

int CAlienWave::GetBottom() const
{
	return m_waveBox.GetBottom();
}